#include "iap_update.h"
#include <string.h>
#include <stdlib.h>
#include "stdcrc16.h"
#include "com.h"
#include "lfs_if.h"
#include "dev_api.h"
#include "proc_bar.h"

#define IAP_SIZE_MINIMUM    8
#define IAP_PREFIX_VALUE    0xAA55
#define IAP_ACCEPT          0xAC
#define IAP_REFUSE          0xEF
#define IAP_REPLY_FLAG      0x8000
#define IAP_DATA_SIZE       518
#define DEBUG(...)
typedef struct iap_frame_s {
  uint16_t control;
  uint16_t length;
  uint8_t data[IAP_DATA_SIZE];
} iap_frame_t;

typedef struct packet_s {
  uint32_t offset;
  uint16_t size;
  uint8_t* pdata;
} packet_t;

const uint16_t prefix = IAP_PREFIX_VALUE;

static int32_t iap_check(uint8_t* pbuf, int32_t size)
{
  uint16_t data_size;
  uint16_t cal_crc16;
  int32_t offset;
  if(size < IAP_SIZE_MINIMUM) return EXIT_FAILURE;
  for(offset=0;offset<=(size-IAP_SIZE_MINIMUM);offset++) {
    if(memcmp(&pbuf[offset], &prefix, 2)) continue;
    memcpy(&data_size, &pbuf[offset+4], 2);
    if((data_size+IAP_SIZE_MINIMUM)>(size-offset)) continue;
    cal_crc16 = CRC16_MODBUS(&pbuf[offset+2], data_size+4);
    if(memcmp(&pbuf[offset+data_size+6], &cal_crc16, 2)) continue;
    if(offset) memmove(&pbuf[0], &pbuf[offset], data_size+IAP_SIZE_MINIMUM);
    return EXIT_SUCCESS;
  }
  return EXIT_FAILURE;
}

static int32_t iap_parse(void* frame, uint8_t* pbuf, int32_t size)
{
  iap_frame_t* p_frame;
  if(frame == NULL) return EXIT_FAILURE;
  if(pbuf == NULL) return EXIT_FAILURE;
  p_frame = (iap_frame_t*)frame;
  memcpy(&p_frame->control, &pbuf[2], 2);
  memcpy(&p_frame->length, &pbuf[4], 2);
  if(p_frame->length > size) return EXIT_FAILURE;
  if(p_frame->length) memcpy(p_frame->data, &pbuf[6], p_frame->length);
  return EXIT_SUCCESS;
}

static int32_t iap_make(void* frame, uint8_t* pbuf, int32_t size)
{
  iap_frame_t* p_frame;
  uint16_t cal_crc16;
  if(frame == NULL) return 0;
  if(pbuf == NULL) return 0;
  p_frame = (iap_frame_t*)frame;
  if(size < p_frame->length+IAP_SIZE_MINIMUM) return 0;
  memcpy(&pbuf[0], &prefix, 2);
  memcpy(&pbuf[2], &p_frame->control, 2);
  memcpy(&pbuf[4], &p_frame->length, 2);
  if(p_frame->length) memcpy(&pbuf[6], p_frame->data, p_frame->length);
  cal_crc16 = CRC16_MODBUS(&pbuf[2], p_frame->length+4);
  memcpy(&pbuf[p_frame->length+6], &cal_crc16, 2);
  return p_frame->length+8;
}

int32_t iap_service(uint8_t* pbuf, int32_t size, uint32_t com_handler)
{
  iap_frame_t frame;
  int32_t write_size = 0;
  static int file_size = 0;
  com_dev_t *p_com = (com_dev_t *)com_handler;
  lfs_file_t *file;
  DEBUG("<iap_service>\n");
  if(pbuf == NULL) {
    DEBUG("return: pbuf == NULL\n");
    return EXIT_FAILURE;
  }
  if(iap_check(pbuf, size)) {
    DEBUG("return: iap_check\n");
    return EXIT_FAILURE;
  }
  if(iap_parse(&frame, pbuf, IAP_DATA_SIZE)) {
    DEBUG("return: iap_parse\n");
    return EXIT_FAILURE;
  }
  if(frame.control & IAP_REPLY_FLAG) return EXIT_FAILURE;
  DEBUG("control = %04X\n", frame.control);
  switch(frame.control) {
  case 0x0000: {
    int available_size;
    if(frame.length != 4) {
      DEBUG("REFUSE: frame.length != 4\n");
      frame.length = 1;
      frame.data[0] = IAP_REFUSE;
      break;
    }
    memcpy(&file_size, frame.data, 4);
    available_size = lfs_df();
    frame.length = 1;
    if(available_size > file_size) {
      file = file_open(UPDATE_TEMP_FILE, LFS_O_RDWR|LFS_O_CREAT);
      if(file == NULL) {
        frame.data[0] = IAP_REFUSE;
      }
      else {
        file_truncate(0, file);
        file_close(file);
        printf("iap update: receiving update.bin ...\n");
        // bar_update(0, file_size);
        frame.data[0] = IAP_ACCEPT;
      }
    }
    else
      frame.data[0] = IAP_REFUSE;
    break;
  } 
  case 0x0001: {
    packet_t packet;
    if(frame.length < 8) {
      DEBUG("REFUSE: frame.length < 8.\n");
      frame.length = 1;
      frame.data[0] = IAP_REFUSE;
      break;
    }
    memcpy(&packet.offset, &frame.data[0], 4);
    memcpy(&packet.size, &frame.data[4], 2);
    packet.pdata = &frame.data[6];
    file = file_open(UPDATE_TEMP_FILE, LFS_O_RDWR|LFS_O_CREAT);
    if(file == NULL) {
      frame.data[0] = IAP_REFUSE;
    }
    else {
      file_seek(file, packet.offset, LFS_SEEK_SET);
      file_write(packet.pdata, packet.size, file);
      file_close(file);
      // bar_update(packet.offset+packet.size, file_size);
      frame.data[0] = IAP_ACCEPT;
    }
    frame.length = 1;
    break;
  }
  case 0x0002: {
    if(frame.length != 0) {
      frame.length = 1;
      frame.data[0] = IAP_REFUSE;
      break;
    }
    frame.length = 1;
    file_size = 0;
    printf("completed.\n");
    if(file_rename(UPDATE_TEMP_FILE, UPDATE_FILE) < 0) 
      frame.data[0] = IAP_REFUSE;
    else 
      frame.data[0] = IAP_ACCEPT;
    break;
  }
  case 0x0003: {
    if(frame.length != 0) {
      DEBUG("REFUSE: frame.length != 0.\n");
      frame.length = 1;
      frame.data[0] = IAP_REFUSE;
      break;
    }
    frame.length = 1;
    frame.data[0] = IAP_ACCEPT;
    frame.control |= IAP_REPLY_FLAG;
    write_size = iap_make(&frame, pbuf, UART_CACHE_SIZE);
    p_com->write(pbuf, write_size);
    HAL_Delay(100);
    p_com->write(pbuf, 0);
    NVIC_SystemReset();
    return EXIT_SUCCESS;
  }
  default: {
    frame.length = 1;
    frame.data[0] = IAP_REFUSE;
    break; 
  }
  }
  frame.control |= IAP_REPLY_FLAG;
  write_size = iap_make(&frame, pbuf, UART_CACHE_SIZE);
  p_com->write(pbuf, write_size);
  return EXIT_SUCCESS;
}
